const express = require('express')
const app = express()
const cors = require('cors')
const multiparty = require('multiparty')
const path = require('path')
const fse = require('fs-extra')

// 使用 express.json() 中间件来解析 JSON 格式的请求体数据
app.use(express.json())

// 提取文件后缀名的函数
const extractExt = (filename) => {
  return filename.slice(filename.lastIndexOf('.'), filename.length)
}

// 允许特定源进行跨域访问
app.use(cors())

// 所有上传的文件存放到该目录下
const UPLOAD_DIR = path.resolve(__dirname, 'uploads')

// 处理上传文件的 POST 请求
app.post('/upload', function (req, res) {
  // 创建一个 multiparty Form 实例，用于解析表单数据
  const form = new multiparty.Form()

  // 使用 multiparty Form 解析请求
  form.parse(req, async function (err, fields, files) {
    // 如果解析出错，则返回 401 错误响应
    if (err) {
      res.status(401).json({
        ok: false,
        msg: '上传失败'
      })
      return
    }

    // 从表单字段中获取文件哈希值和切片哈希值
    const fileHash = fields['fileHash'][0]
    const chunkHash = fields['chunkHash'][0]

    // 获取切片文件的临时路径
    const filePath = files['chunk'][0]['path']

    // 构建切片存储目录路径
    const chunkDir = path.resolve(UPLOAD_DIR, fileHash)

    // 如果切片存储目录不存在，则创建它
    if (!fse.existsSync(chunkDir)) {
      await fse.mkdirs(chunkDir)
    }

    // 构建切片目标路径
    const targetPath = path.resolve(chunkDir, chunkHash)

    // 如果切片已存在，就移除它，否则 fse.move 会报错
    if (fse.existsSync(targetPath)) {
      await fse.remove(targetPath)
    }

    // 将切片文件移动到目标路径
    await fse.move(filePath, targetPath)

    // 返回成功上传的响应
    res.status(200).json({
      ok: true,
      msg: '上传成功'
    })
  })
})

// 合并切片文件
app.post('/merge', async function (req, res) {
  const { fileHash, fileName, size } = req.body

  // 构建文件最终保存路径
  const filePath = path.resolve(UPLOAD_DIR, `${fileHash}${extractExt(fileName)}`)

  // 如果大文件已经存在，则直接返回
  if (fse.existsSync(filePath)) {
    res.status(200).json({
      ok: true,
      msg: '合并成功'
    })
    return
  }

  // 构建切片存储目录路径
  const chunkDir = path.resolve(UPLOAD_DIR, fileHash)

  // 切片目录不存在，则无法合并切片，返回错误
  if (!fse.existsSync(chunkDir)) {
    res.status(200).json({
      ok: false,
      msg: '合并失败，请重新上传'
    })
    return
  }

  // 获取切片目录下的所有切片文件列表
  const chunkPaths = await fse.readdir(chunkDir)

  // 对切片文件列表按照切片索引排序
  chunkPaths.sort((a, b) => a.split('-')[1] - b.split('-')[1])

  // 合并切片文件
  // 创建一个Promise数组，用于存储每个切片文件的处理Promise
  const list = chunkPaths.map((item, index) => {
    return new Promise((resolve, reject) => {
      // 获取切片文件的完整路径
      const chunkPath = path.resolve(chunkDir, item)

      // 创建可读流以从切片文件中读取数据
      const readStream = fse.createReadStream(chunkPath)

      // 创建可写流以将数据写入目标文件，指定写入的位置范围
      const writeStream = fse.createWriteStream(filePath, {
        start: index * size, // 写入的起始位置
        end: (index + 1) * size // 写入的结束位置（不包含）
      })

      // 当切片合并完成后执行的回调函数
      readStream.on('end', async () => {
        // 删除已合并的切片文件
        await fse.unlink(chunkPath)
        // 解析Promise，表示当前切片处理完成
        resolve()
      })

      // 使用管道将读取的数据流传输到写入的目标文件中
      readStream.pipe(writeStream)
    })
  })

  // 等待所有切片合并任务完成
  await Promise.all(list)

  // 删除切片目录
  await fse.remove(chunkDir)

  // 返回合并成功的响应
  res.status(200).json({
    ok: true,
    msg: '合并成功'
  })
})

// 根据文件hash验证文件有没有上传过
app.post('/verify', async (req, res) => {
  const { fileHash, fileName } = req.body
  const filePath = path.resolve(UPLOAD_DIR, `${fileHash}${extractExt(fileName)}`)
  // 断点上传
  // 返回服务器已经上传好了的切片
  const chunkDir = path.resolve(UPLOAD_DIR, fileHash)
  let chunkPaths = []
  if (fse.existsSync(chunkDir)) {
    chunkPaths = await fse.readdir(chunkDir)
  }

  // console.log(chunkPaths, 156)
  if (fse.existsSync(filePath)) {
    // 文件存在服务器中，不需要再上传了
    res.status(200).json({
      ok: true,
      data: {
        shouldUpload: false
      }
    })
  } else {
    // 文件不在服务器中，就需要上传
    res.status(200).json({
      ok: true,
      data: {
        shouldUpload: true,
        existChunks: chunkPaths
      }
    })
  }
})
// 启动 Express 服务器，监听端口 3000
app.listen(3000, () => {
  console.log('Server is running on port 3000')
})
